commonlibsse_ng\re\c/ConsoleLog.rs
1use core::ffi::c_char;
2use core::fmt;
3use std::ffi::CString;
4
5use crate::re::{BSString::BSString, TLSData::TLSData};
6
7/// We have confirmed that any attempt to `print` beyond this size will result in a definite crash.
8const LAST_MESSAGE_BUFFER_SIZE: usize = 0x400;
9
10/// # Note
11/// The console has also confirmed that it does not support ANSI Color.
12#[repr(C)]
13pub struct ConsoleLog {
14 pub lastMessage: [c_char; LAST_MESSAGE_BUFFER_SIZE],
15 pub pad401: u8,
16 pad402: u16,
17 pad404: u32,
18 buffer: BSString,
19}
20const _: () = assert!(core::mem::size_of::<ConsoleLog>() == 0x418);
21
22impl ConsoleLog {
23 /// Returns the singleton instance of `Self`.
24 #[commonlibsse_ng_derive_internal::relocate(
25 cast_as = "*mut *mut ConsoleLog",
26 default = "None",
27 deref_once,
28 id(se = 515064, ae = 401203)
29 )]
30 #[inline]
31 pub fn get_singleton() -> Option<&'static ConsoleLog> {
32 |deref_type: DerefType| unsafe { deref_type.as_ref() }
33 }
34
35 /// Returns the mutable singleton instance of `Self`.
36 #[commonlibsse_ng_derive_internal::relocate(
37 cast_as = "*mut *mut ConsoleLog",
38 default = "None",
39 deref_once,
40 id(se = 515064, ae = 401203)
41 )]
42 #[inline]
43 pub unsafe fn get_singleton_mut() -> Option<&'static mut ConsoleLog> {
44 |deref_type: DerefType| unsafe { deref_type.as_mut() }
45 }
46
47 #[inline]
48 pub fn is_console_mode() -> bool {
49 TLSData::get_static_tls_data().is_some_and(|tls| unsafe { tls.as_ref().consoleMode })
50 }
51
52 /// Print argument c-string.
53 /// # Example
54 /// ```no_run
55 /// use commonlibsse_ng::re::ConsoleLog::ConsoleLog;
56 ///
57 /// if let Some(console) = unsafe { ConsoleLog::get_singleton_mut() } {
58 /// console.print(c"Hello World!".as_ptr());
59 /// };
60 /// ```
61 ///
62 /// # Note
63 /// More precisely, args: va_list follows after fmt, but since Rust does not support variadic arguments, it is omitted.
64 #[commonlibsse_ng_derive_internal::relocate_fn(se_id = 50180, ae_id = 51110)]
65 #[inline]
66 pub fn print(&mut self, fmt: *const c_char) {}
67}
68
69/// Prints a formatted string to the in-game console (Skyrim).
70///
71/// This function takes a [`fmt::Arguments`] object and formats it as a string,
72/// then passes it to the native `ConsoleLog::print` function.
73///
74/// # Examples
75///
76/// ```no_run
77/// use commonlibsse_ng::re::ConsoleLog::print_fmt;
78/// print_fmt(format_args!("Health: {}", 100));
79/// ```
80///
81/// # Notes
82/// - This function allocates on the heap due to `CString::new`.
83/// - If the input string contains null bytes (`\0`), it will return early and not print anything.
84/// - This is intended for internal use by [`console_print!`] and [`console_println!`] macros.
85///
86/// # Safety
87/// Internally uses a mutable reference to the singleton `Console`, accessed via `unsafe`.
88#[inline]
89pub fn print_fmt(args: fmt::Arguments) {
90 if let Some(console) = unsafe { ConsoleLog::get_singleton_mut() } {
91 let s = format!("{}", args);
92
93 // We have confirmed that any attempt to `print` beyond this size will result in a definite crash.
94 for chunk in s.as_bytes().chunks(LAST_MESSAGE_BUFFER_SIZE) {
95 // Only try to print valid UTF-8 chunks
96 if let Ok(chunk_str) = core::str::from_utf8(chunk) {
97 if let Ok(c_str) = CString::new(chunk_str) {
98 console.print(c_str.as_ptr());
99 }
100 }
101 }
102 }
103}